﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using BitsOfStuff.Support;

namespace BitsOfStuff
{
    /// <summary>
    /// Class that provide pacit analysis of corner points of shape
    /// </summary>
    public class BasicCornerAnalysis
    {
        private static MyPoint[] _shapePoints; //HoldsDrawn point internaly
        private static bool[] _resultBitMap;
        private static bool[] _resultBitMapSmooth;

        /// <summary>
        /// Array that hold result of this analysing , used for multi thrad operations
        /// </summary>
        public static bool[] Result;

        /// <summary>
        /// Holds partial results of cornerStrenght that comes from paralel calculation
        /// </summary>
        public static double[] CornerStrengPartialResult;
        /// <summary>
        /// Holds partial results of cornerStreng that comes from partial calculation based on indexes
        /// </summary>
        public static double[] CornerStrengPartialResultAvrg;

        /// <summary>
        /// Field of value for debuging purposed
        /// </summary>
        public static double[] DebugCorner;

        /// <summary>
        /// Field of value for debuign purposed in paralel calculation
        /// </summary>
        public static double[] DebugCornerThread;

        //Constructor of BasicCurved analysis ,
        //Parameter precion tell procedure, what is the smallest recognazed angle between n-points

        //Main procedure of BasicCurved analysis . It return bool array ( bitmap)
        // if at position "i" is true , that mean that point at same position have been selected as
        // point of changing direction
        private static bool[] getPointBitMap(MyPoint[] myPoints)
        {
            _shapePoints = myPoints;
            _resultBitMap = new bool[myPoints.Count()];
            selectionProcedure();
            return _resultBitMap;
        }

        private static bool[] getPointBitMapSmooth(MyPoint[] myPoints)
        {
            _shapePoints = myPoints;
            _resultBitMapSmooth = new bool[myPoints.Count()];
            smoothSelectionProcedure();
            return _resultBitMapSmooth;
        }

        private static MyPoint forwardPoint(int offset, int pointIndex)
        {
            //Handle , when we want pick point , that doesnt exist , change offset for closest point
            if ((pointIndex + offset) >= _shapePoints.Length)
            {
                offset = _shapePoints.Length - pointIndex - 1;
            }

            var result = new MyPoint();
            result.setX(_shapePoints[pointIndex].getX() - _shapePoints[pointIndex + offset].getX());
            result.setY(_shapePoints[pointIndex].getY() - _shapePoints[pointIndex + offset].getY());
            return result;
        }

        private static MyPoint backwardPoint(int offset, int pointIndex)
        {
            //Handle , when we want pick point , that doesnt exist , change offset for closest point
            if ((pointIndex - offset) < 0)
            {
                offset = -pointIndex;
            }

            var result = new MyPoint();
            result.setX(_shapePoints[pointIndex].getX() - _shapePoints[pointIndex - offset].getX());
            result.setY(_shapePoints[pointIndex].getY() - _shapePoints[pointIndex - offset].getY());
            return result;
        }

        private static double cornerStrength(int offset, int pointIndex)
        {
            MyPoint forwardPoint = BasicCornerAnalysis.forwardPoint(offset, pointIndex);
            MyPoint backwardPoint = BasicCornerAnalysis.backwardPoint(offset, pointIndex);

            double div = (forwardPoint.getX() * backwardPoint.getX() + backwardPoint.getY() * forwardPoint.getY());
            double divider = forwardPoint.getPointLength() * backwardPoint.getPointLength();
            if (!double.IsNaN((div / divider)))
            {
                //debugCorner[pointIndex] = div / divider;
                return div / divider;
            }
            return div;
        }

        /*
                private static void cornerStrengthThread(int offset, int pointIndex, int resultIndex)
                {
                    MyPoint forwardPoint = BasicCornerAnalysis.forwardPoint(offset, pointIndex);
                    MyPoint backwardPoint = BasicCornerAnalysis.backwardPoint(offset, pointIndex);

                    double div = (forwardPoint.getX() * backwardPoint.getX() + backwardPoint.getY() * forwardPoint.getY());
                    double divider = forwardPoint.getPointLength() * backwardPoint.getPointLength();
                    if (!double.IsNaN((div / divider)))
                    {
                        //debugCornerThread[pointIndex] = (div / divider);
                        CornerStrengPartialResult[resultIndex] = (div / divider);
                    }
                    else
                    {
                        CornerStrengPartialResult[resultIndex] = div;
                        //debugCornerThread[pointIndex] = div;
                    }
                }
        */

        private static double avrgCornerStrengthOdd(int offset, int pointIndex)
        {
            double partialResult = 0;
            for (int i = ((offset - 1) / 2); i < offset; i++)
            {
                partialResult = cornerStrength(i, pointIndex) + partialResult;
            }
            double x = (double)2 / (offset + 3);
            double result = partialResult * x;
            //debugCorner[pointIndex] = result;
            return result;
        }

        private static void avrgCornerStrengthOddThread(int offset, int pointIndex, int resultIndex)
        {
            double partialResult = 0;
            for (int i = ((offset - 1) / 2); i < offset; i++)
            {
                partialResult = cornerStrength(i, pointIndex) + partialResult;
            }
            double x = (double)2 / (offset + 3);
            double result = partialResult * x;
            //debugCornerThread[pointIndex] = result;
            CornerStrengPartialResultAvrg[resultIndex] = result;
        }

        private static double avrgCornerStrengthEven(int offset, int pointIndex)
        {
            double partialResult = 0;
            for (int i = (offset / 2); i <= offset; i++)
            {
                partialResult = cornerStrength(i, pointIndex) + partialResult;
            }
            double x = (double)2 / (offset + 2);
            double result = partialResult * x;
            //debugCorner[pointIndex] = result;
            return result;
        }

        private static void avrgCornerStrengthEvenThread(int offset, int pointIndex, int resultIndex)
        {
            double partialResult = 0;
            for (int i = (offset / 2); i <= offset; i++)
            {
                partialResult = cornerStrength(i, pointIndex) + partialResult;
            }
            double x = (double)2 / (offset + 2);
            double result = partialResult * x;
            //debugCornerThread[pointIndex] = result;
            CornerStrengPartialResultAvrg[resultIndex] = result;
        }

        private static void selectionProcedureThread()
        {
            var n = _shapePoints.Count();
            var m = (int)Math.Round(n * MyParameters.cornerHighCurvedPrecisionParameter);
            _resultBitMap = new bool[n];
            int cornerCount = 1;
            for (int i = 0; i < _shapePoints.Length; i = i + m)
            {
                cornerCount++;
            }
            CornerStrengPartialResult = new double[cornerCount];
            var complete = new CountdownEvent(1);
            int resultIndex = 0;

            for (int i = 0; i < _shapePoints.Length; i = i + m)
            {
                int i1 = i;
                int index = resultIndex;
                var tryAddCount = complete.TryAddCount();
                //Debug.WriteLine(String.Format("adding thread for point {0}", i1));
                ThreadPool.QueueUserWorkItem(state =>
                                    {
                                        try
                                        {
                                            cornerStrength(i1, index);
                                        }
                                        finally
                                        {
                                            //Debug.WriteLine(String.Format("finishing thread for point {0}", i1));
                                            lock (complete)
                                                complete.Signal();
                                        }
                                    }, null);
                resultIndex++;
            }
            complete.Signal();
            complete.Wait();
            var cos = new List<double>(CornerStrengPartialResult);
            var peaks = MyMath.findPeaks(cos, MyParameters.cornerMinimalRecognizedAngle);
            foreach (var peak in peaks)
            {
                _resultBitMap[peak * m] = true;
            }
        }

        private static void selectionProcedure()
        {
            var n = _shapePoints.Count();
            _resultBitMap = new bool[n];
            var m = (int)Math.Round(n * MyParameters.cornerHighCurvedPrecisionParameter);

            var cos = new List<double>();
            var selectedPointsIndexes = new List<int>();
            for (int i = 0; i < _shapePoints.Length; i = i + m)
            {
                cos.Add(cornerStrength(m, i));
                selectedPointsIndexes.Add(i);
            }
            List<int> peaks = MyMath.findPeaks(cos, MyParameters.cornerMinimalRecognizedAngle);
            foreach (int peak in peaks)
            {
                _resultBitMap[peak * m] = true;
            }
        }

        private static void smoothSelectionProcedureThread()
        {
            var n = _shapePoints.Count();
            _resultBitMapSmooth = new bool[n];
            var m = (int)Math.Round(n * MyParameters.cornerHighCurvedPrecisionParameter);
            int cornerCount = 1;
            for (int i = 0; i < _shapePoints.Length; i = i + m)
            {
                cornerCount++;
            }
            CornerStrengPartialResultAvrg = new double[cornerCount];
            var complete = new CountdownEvent(1);
            int resultIndex = 0;
            if ((m % 2) == 0)
            {
                for (int i = 0; i < _shapePoints.Length; i = i + m)
                {
                    int i1 = i;
                    complete.TryAddCount();
                    //Debug.WriteLine(String.Format("AVRG adding thread for point {0}", i1));
                    int index = resultIndex;
                    ThreadPool.QueueUserWorkItem(state =>
                    {
                        try
                        {
                            avrgCornerStrengthEvenThread(m, i1, index);
                        }
                        finally
                        {
                            //Debug.WriteLine(String.Format("AVRG finishing thread for point {0}", i1));
                            lock (complete)
                                complete.Signal();
                        }
                    }, null);
                    resultIndex++;
                }
            }
            else
            {
                for (int i = 0; i < _shapePoints.Length; i = i + m)
                {
                    int i1 = i;
                    complete.TryAddCount();
                    //Debug.WriteLine(String.Format("AVRG adding thread for point {0}", i1));
                    int index = resultIndex;
                    ThreadPool.QueueUserWorkItem(state =>
                    {
                        try
                        {
                            avrgCornerStrengthOddThread(m, i1, index);
                        }
                        finally
                        {
                            //Debug.WriteLine(String.Format("AVRG finishing thread for point {0}", i1));
                            lock (complete)
                                complete.Signal();
                        }
                    }, null);
                    resultIndex++;
                }
            }
            complete.Signal();
            complete.Wait();
            var cos = new List<double>(CornerStrengPartialResultAvrg);
            DebugCornerThread = cos.ToArray();
            List<int> peaks = MyMath.findPeaks(cos, MyParameters.cornerMinimalRecognizedAngle);
            foreach (var peak in peaks)
            {
                _resultBitMapSmooth[peak * m] = true;
            }
        }

        private static void smoothSelectionProcedure()
        {
            var n = _shapePoints.Count();
            var m = (int)Math.Round(n * MyParameters.cornerHighCurvedPrecisionParameter);

            var cos = new List<double>();
            var selectedPointsIndexes = new List<int>();
            if ((m % 2) == 0)
            {
                for (int i = 0; i < _shapePoints.Length; i = i + m)
                {
                    cos.Add(avrgCornerStrengthEven(m, i));
                    selectedPointsIndexes.Add(i);
                }
            }
            else
            {
                for (int i = 0; i < _shapePoints.Length; i = i + m)
                {
                    cos.Add(avrgCornerStrengthOdd(m, i));
                    selectedPointsIndexes.Add(i);
                }
            }
            DebugCorner = cos.ToArray();
            List<int> peaks = MyMath.findPeaks(cos, MyParameters.cornerMinimalRecognizedAngle);
            foreach (int peak in peaks)
            {
                _resultBitMap[peak * m] = true;
            }
        }

        /// <summary>
        /// Function for analysing shape for result in form of corner points that works in THREADS!!!
        /// </summary>
        /// <param name="myPoints">Raw points</param>
        /// <returns>bitmap of corner points</returns>
        public static bool[] analyseThread(MyPoint[] myPoints)
        {
            var partialBitMaps = new List<bool[]> { SupportFunctions.makeStartEnd(myPoints.Length) };
            //partialBitMaps.Add(getPointBitMap(myPoints)); // basic Corner Points
            //partialBitMaps.Add(getPointBitMapSmooth(myPoints)); //Average Corner Strengh analysis
            _shapePoints = myPoints;
            var complete = new CountdownEvent(1);
            //var analysis = new List<object> {getPointBitMapSmooth(myPoints), getPointBitMap(myPoints)};
            complete.TryAddCount();
            ThreadPool.QueueUserWorkItem(state =>
                                                    {
                                                        try
                                                        {
                                                            //Debug.WriteLine("<------Starting thread for corner analyseThread------>");
                                                            selectionProcedure();
                                                        }
                                                        finally
                                                        {
                                                            //Debug.WriteLine(String.Format("<------Finishing thread for corner analyseThread------>"));
                                                            lock (complete)
                                                                complete.Signal();
                                                        }
                                                    }, null);
            complete.TryAddCount();
            ThreadPool.QueueUserWorkItem(state =>
                                                    {
                                                        try
                                                        {
                                                            //Debug.WriteLine("<------Starting thread for corner AVRG analyseThread------>");
                                                            smoothSelectionProcedureThread();
                                                        }
                                                        finally
                                                        {
                                                            //Debug.WriteLine(String.Format("<------Finishing thread for corner AVRG analyseThread------>"));
                                                            lock (complete)
                                                                complete.Signal();
                                                        }
                                                    }, null);

            complete.Signal();
            complete.Wait();
            partialBitMaps.Add(_resultBitMap);
            partialBitMaps.Add(_resultBitMapSmooth);
            //Debug.WriteLine(String.Format("<------Merging curved analysis results------>"));
            var result = SupportFunctions.mergeNearPoints(partialBitMaps, MyParameters.cornerMargedPointDistance);
            return result;
        }

        /// <summary>
        /// Function for analysing shape for result in form of corner points that work only in ONE THREAD!!!
        /// </summary>
        /// <param name="myPoints">RAW points</param>
        /// <returns>bitmaps of corner points</returns>
        public static bool[] analyse(MyPoint[] myPoints)
        {
            var partialBitMaps = new List<bool[]>
                                     {
                                         SupportFunctions.makeStartEnd(myPoints.Length),
                                         getPointBitMap(myPoints),
                                         getPointBitMapSmooth(myPoints)
                                     };
            var result = SupportFunctions.mergeNearPoints(partialBitMaps, MyParameters.cornerMargedPointDistance);
            return result;
        }

        /// <summary>
        /// generic function that perform analyis of raw points
        /// </summary>
        /// <param name="o"></param>
        public static void analyse(object o)
        {
            var points = (MyPoint[])o;
            Result = new bool[points.Length];
            Result = analyseThread(points);
        }

        /// <summary>
        /// Debug function that provide test between single thread and multi thread operatins
        /// </summary>
        /// <returns></returns>
        public static bool debugTest()
        {
            if (DebugCornerThread.Length != DebugCorner.Length)
                return false;
            bool result = true;
            for (int i = 0; i < DebugCorner.Length; i++)
            {
                if (DebugCornerThread[i].CompareTo(DebugCorner[i]) == 0)
                    result = result && true;
                else
                {
                    result = false;
                }
            }
            return result;
        }
    }
}